import 'reflect-metadata';
import BeanDefinition from "@framework/bean/BeanDefinition";
import KoaBootRouter from "@framework/web/KoaBootRouter";
import HttpEnum from "@framework/enum/HttpEnum";
import KoaBootRoutes from "@framework/web/KoaBootRoutes";
import StrUtils from "@framework/util/StrUtils";

/**
 * 装饰器反射键枚举
 */
export const DECORATOR_SUFFIX = {
    BEAN: "Bean",
    WEB: "Web",
    CONTEXT: "Context",
    QUERY: "Query",
    BODY: "Body",
    PROPERTY: "Property",
    GET: "Get",
    POST: "Post",
    generator: (prefix: string, suffix: string): string => {
        return `${prefix}${suffix}`;
    }
}

/**
 * 组件装饰器
 * @param target
 * @constructor
 */
export function Component(target: any) {
    const beanDefinition = getBeanDefinition(DECORATOR_SUFFIX.generator(target.name, DECORATOR_SUFFIX.BEAN), target);
    beanDefinition.setTarget(target);
    Reflect.defineMetadata(DECORATOR_SUFFIX.generator(target.name, DECORATOR_SUFFIX.BEAN), beanDefinition, target);
}

/**
 * 控制层装饰器
 * @param target
 * @constructor
 */
export function Controller(target: any) {
    const beanDefinition = getBeanDefinition(DECORATOR_SUFFIX.generator(target.name, DECORATOR_SUFFIX.BEAN), target);
    beanDefinition.setTarget(target);
    Reflect.defineMetadata(DECORATOR_SUFFIX.generator(target.name, DECORATOR_SUFFIX.BEAN), beanDefinition, target);
}

/**
 * 服务层装饰器
 * @param target
 * @constructor
 */
export function Service(target: any) {
    const beanDefinition = getBeanDefinition(DECORATOR_SUFFIX.generator(target.name, DECORATOR_SUFFIX.BEAN), target);
    beanDefinition.setTarget(target);
    Reflect.defineMetadata(DECORATOR_SUFFIX.generator(target.name, DECORATOR_SUFFIX.BEAN), beanDefinition, target);
}

/**
 * Dao层装饰器
 * @param target
 * @constructor
 */
export function Repository(target: any) {
    const beanDefinition = getBeanDefinition(DECORATOR_SUFFIX.generator(target.name, DECORATOR_SUFFIX.BEAN), target);
    beanDefinition.setTarget(target);
    Reflect.defineMetadata(DECORATOR_SUFFIX.generator(target.name, DECORATOR_SUFFIX.BEAN), beanDefinition, target);
}

/**
 * 自动注入装饰器
 * @param target
 * @param propertyKey
 * @constructor
 */
export function Autowired(target: any, propertyKey: string) {
    const beanDefinition = getBeanDefinition(DECORATOR_SUFFIX.generator(target.constructor.name, DECORATOR_SUFFIX.BEAN), target.constructor);
    let injectProperty: Array<Map<string, string>> = beanDefinition.getInjectProperty();
    if (!injectProperty) {
        injectProperty = new Array<Map<string, string>>();
    }
    const property: Map<string, string> = new Map();
    const propertyType = Reflect.getMetadata('design:type', target, propertyKey)
    property.set(propertyKey, propertyType.name);
    injectProperty.push(property);
    beanDefinition.setInjectProperty(injectProperty);
    Reflect.defineMetadata(DECORATOR_SUFFIX.generator(target.constructor.name, DECORATOR_SUFFIX.BEAN), beanDefinition, target.constructor);
}

/**
 * 路由前缀装饰器
 */
export function RequestMapping(prefix: string) {
    return (target: any) => {
        const metadataKey = DECORATOR_SUFFIX.generator(target.name, DECORATOR_SUFFIX.WEB);
        const koaBootRouter = getKoaBootRouter(metadataKey, target);
        koaBootRouter.setPrefix(prefix);
        Reflect.defineMetadata(metadataKey, koaBootRouter, target);
    }
}

/**
 * Get请求映射
 */
export function GetMapping(url: string) {
    return (target: any, propertyKey: string) => {
        const returnType = Reflect.getMetadata('design:returntype', target, propertyKey);
        const metadataKey = DECORATOR_SUFFIX.generator(target.constructor.name, DECORATOR_SUFFIX.WEB);
        const koaBootRouter = getKoaBootRouter(metadataKey, target.constructor);
        const routes = new KoaBootRoutes();
        routes.setFunName(propertyKey);
        routes.setUrl(url);
        routes.setMethod(HttpEnum.GET);
        routes.setReturnType(returnType != undefined || returnType != null)
        koaBootRouter.addRoutes(routes);
        Reflect.defineMetadata(metadataKey, koaBootRouter, target.constructor);
    }
}

/**
 * Post请求映射
 */
export function PostMapping(url: string) {
    return (target: any, propertyKey: string) => {
        const returnType = Reflect.getMetadata('design:returntype', target, propertyKey);
        const metadataKey = DECORATOR_SUFFIX.generator(target.constructor.name, DECORATOR_SUFFIX.WEB);
        const koaBootRouter = getKoaBootRouter(metadataKey, target.constructor);
        const routes = new KoaBootRoutes();
        routes.setFunName(propertyKey);
        routes.setUrl(url);
        routes.setMethod(HttpEnum.POST);
        routes.setReturnType(returnType != undefined || returnType != null)
        koaBootRouter.addRoutes(routes);
        Reflect.defineMetadata(metadataKey, koaBootRouter, target.constructor);
    }
}

/**
 * 自动生成 get set 方法
 * @param target
 * @param propertyKey
 * @constructor
 */
export function Property(target: any, propertyKey: string) {
        const property = StrUtils.capitalizeFirstLetter(propertyKey);
        const metadataKey = DECORATOR_SUFFIX.generator(target.constructor.name, DECORATOR_SUFFIX.PROPERTY);
        if (Reflect.hasMetadata(metadataKey, target.constructor)) {
            const metadata = Reflect.getMetadata(metadataKey, target.constructor);
            metadata.push({key: propertyKey.toString(), value: property})
            Reflect.defineMetadata(metadataKey, metadata, target.constructor);
        } else {
            const metadata: KV[] = [];
            metadata.push({key: propertyKey.toString(), value: property});
            Reflect.defineMetadata(metadataKey, metadata, target.constructor);
        }
}

/**
 * 标记参数是 ctx 类
 * @param target
 * @param propertyKey
 * @constructor
 */
export function KoaContext(target: any, propertyKey: string, parameterIndex: number) {
    const metadataKey = DECORATOR_SUFFIX.generator(target.constructor.name, DECORATOR_SUFFIX.CONTEXT) + propertyKey
    Reflect.defineMetadata(metadataKey, parameterIndex, target.constructor);
}

/**
 * 参数标记
 * @param target
 * @param propertyKey
 * @constructor
 */
export function RequestParam(name: string) {
    return (target: any, propertyKey: string, parameterIndex: number) => {
        const metadataKey = DECORATOR_SUFFIX.generator(target.constructor.name, DECORATOR_SUFFIX.QUERY) + propertyKey
        const array: Array<Map<string, number>> = getQueryParameterIndexArray(metadataKey, target.constructor);
        const map: Map<string, number> = new Map();
        map.set(name, parameterIndex);
        array.push(map);
        Reflect.defineMetadata(metadataKey, array, target.constructor);
    }
}

/**
 * 获取body参数
 * @param target
 * @param propertyKey
 * @param parameterIndex
 * @constructor
 */
export function RequestBody(target: any, propertyKey: string, parameterIndex: number){
        const metadataKey = DECORATOR_SUFFIX.generator(target.constructor.name, DECORATOR_SUFFIX.BODY) + propertyKey
        const array: Array<number> = getBodyParameterIndexArray(metadataKey, target.constructor);
        array.push(parameterIndex);
        Reflect.defineMetadata(metadataKey, array, target.constructor);
}

/**
 * 获取路由对象
 * @param metadataKey
 * @param target
 */
function getKoaBootRouter(metadataKey: string, target: any): KoaBootRouter {
    if (Reflect.hasOwnMetadata(metadataKey, target)) {
        return Reflect.getOwnMetadata(metadataKey, target);
    }
    return new KoaBootRouter();
}

/**
 * 获取Bean描述类
 * @param metadataKey
 * @param target
 */
function getBeanDefinition(metadataKey: string, target: any): BeanDefinition {
    if (Reflect.hasOwnMetadata(metadataKey, target)) {
        return Reflect.getOwnMetadata(metadataKey, target);
    }
    return new BeanDefinition();
}

/**
 * 获取Query参数下标
 * @param metadataKey
 * @param target
 */
function getQueryParameterIndexArray(metadataKey: string, target: any): Array<Map<string, number>> {
    const array = Reflect.getMetadata(metadataKey, target);
    if (array) {
        return array;
    }
    return new Array();
}

/**
 * 获取Body参数下表
 * @param metadataKey
 * @param constructor
 */
function getBodyParameterIndexArray(metadataKey: string, target: any): Array<number> {
    const array = Reflect.getMetadata(metadataKey, target);
    if (array) {
        return array;
    }
    return new Array();
}
